//-------------------------------------------------------------------
//	Copyright (c) 2010-2012  Zhirnov Andrey
//	This file is part of the "UXGEN" project.
//	See copyright notice in "Engine.h".
//-------------------------------------------------------------------

#ifndef _UX_MATERIAL_LAYER_H
#define _UX_MATERIAL_LAYER_H

#include "CMaterial.h"
#include "../Programs/IProgram.h"
#include "../Textures/Textures.h"
#include "../../Systems/Render/CRender.h"

namespace Engine
{
namespace Resources
{


	//
	// Material Layer template
	//

	template <typename DM, typename PC, typename RS>
	class CMaterialLayer : public IMaterial::IMaterialLayer
	{
	public:
		typedef array< TMaterialTexture >		texture_array_t;
		typedef DM								DataManager_t;
		typedef PC								ProgramCreator_t;
		typedef RS								RenderStates_t;

	protected:
		DataManager_t				_cDataManager;
		ProgramCreator_t			_cProgramCreator;
		RenderStates_t				_cRenderStates;
		texture_array_t				_aTextures;
		e_render_layer::type		_eLayer;
		uint8						_uLOD;
		bool						_bLock		: 1,	// if true then can't change data & tex after loading
									_bChanged	: 1;

	public:
		CMaterialLayer();
		~CMaterialLayer();
		
		bool Create(e_render_layer::type eLayer, uint8 uLOD);
		void Destroy();

		DataManager_t *			GetDataManager()			{ return &_cDataManager; }
		ProgramCreator_t *		GetProgramCreator()			{ return &_cProgramCreator; }
		RenderStates_t *		GetRenderStates()			{ return &_cRenderStates; }

		DataManager_t const *	GetDataManager()	const	{ return &_cDataManager; }
		ProgramCreator_t const*	GetProgramCreator()	const	{ return &_cProgramCreator; }
		RenderStates_t const *	GetRenderStates()	const	{ return &_cRenderStates; }
		
		void LockData(bool bLock);
		bool AddTexture(const TMaterialTexture &sTex);


		// IMaterialLayer //
		virtual void  Apply();
		virtual bool  Link(const IProgramPtr &pProgram, uni_array<uint32> aStages);
		virtual bool  GetLinkProgram(const uni_array<const GLVertexAttribInfo> &aMeshAttribs,
										IProgramPtr &pProg, uni_array<uint32> aStages);
		virtual uint GetMaterialLOD()					const	{ return _uLOD; }
		virtual e_render_layer::type GetRenderLayer()	const	{ return _eLayer; }
	};



	//
	// Const Program Data
	//

	class CConstProgramData
	{
	private:
		usize					_uUBIndex;		// index of uniform block
		usize					_uDataSize;
		uint					_uBindingIndex;
		e_program_data::type	_eDataType;
		bool					_bLock;

	public:
		CConstProgramData();
		~CConstProgramData();

		void Apply();
		bool Create();
		void Destroy();
		bool SetData(e_program_data::type eType, bool bConst, const uni_array<const uint8> &aData);
		bool SetData(e_program_data::type eType, CUniformManager::idx_off_uni_c_array_t &aIdxOff, usize uDataSize);
		bool Link(const IProgramPtr &pProgram);
		void Lock(bool bLock);
		/*
		template <typename T>
		T	&		GetData()			{ STATIC_ASSERT( sizeof(T) == _aData.Size() );  return *(T*)_aData.ptr(); }

		template <typename T>
		T const &	GetData()	const	{ STATIC_ASSERT( sizeof(T) == _aData.Size() );  return *(T*)_aData.ptr(); }*/
	};


	
//------------------------- CMaterialLayer ------------------------//
	template <typename DM, typename PC, typename RS>
	inline CMaterialLayer<DM,PC,RS>::CMaterialLayer():
		_eLayer(e_render_layer::UNKNOWN), _uLOD(0), _bLock(false), _bChanged(false)
	{}


	template <typename DM, typename PC, typename RS>
	inline CMaterialLayer<DM,PC,RS>::~CMaterialLayer()
	{}
		

	template <typename DM, typename PC, typename RS>
	inline bool CMaterialLayer<DM,PC,RS>::Create(e_render_layer::type eLayer, uint8 uLOD)
	{
		Destroy();

		_cDataManager.Create();
		_cRenderStates.Create();
		_cProgramCreator.Create();

		_eLayer	= eLayer;
		_uLOD	= uLOD;
		return true;
	}


	template <typename DM, typename PC, typename RS>
	inline void CMaterialLayer<DM,PC,RS>::Destroy()
	{
		_cDataManager.Destroy();
		_cRenderStates.Destroy();
		_cProgramCreator.Destroy();

		_aTextures.Clear();
		_eLayer		= e_render_layer::UNKNOWN;
		_uLOD		= 0;
		_bLock		= false;
		_bChanged	= false;
	}
	

	template <typename DM, typename PC, typename RS>
	inline void CMaterialLayer<DM,PC,RS>::LockData(bool bLock)
	{
		_bLock = bLock;
		_cDataManager.Lock( _bLock );
		_cRenderStates.Lock( _bLock );
	}

	
	template <typename DM, typename PC, typename RS>
	inline bool CMaterialLayer<DM,PC,RS>::AddTexture(const TMaterialTexture &sTex)
	{
		CHECK_VALUE( !_bLock );
		_aTextures.PushBack( sTex );
		_bChanged = true;
		return true;
	}
	

	template <typename DM, typename PC, typename RS>
	inline void CMaterialLayer<DM,PC,RS>::Apply()
	{
		_cDataManager.Apply();

		FOR( i, _aTextures )
		{
			TMaterialTexture const &	tex = _aTextures[i];

			tex.pTexture->Bind( tex.uStage );
		}
	}

	template <typename DM, typename PC, typename RS>
	inline bool CMaterialLayer<DM,PC,RS>::Link(const IProgramPtr &pProgram, uni_array<uint32> aStages)
	{
		CHECK_RETURN( pProg->LinkTextures( _aTextures, aStages ) );
		CHECK_RETURN( _cDataManager.Link( pProg ) );
		return true;
	}

	template <typename DM, typename PC, typename RS>
	inline bool CMaterialLayer<DM,PC,RS>::GetLinkProgram(const uni_array<const GLVertexAttribInfo> &aMeshAttribs,
														IProgramPtr &pProg, uni_array<uint32> aStages)
	{
		CHECK_RETURN( _cProgramCreator.CreateProgram( sMeshAttribs, pProg ) );
		CHECK_RETURN( pProg->LinkTextures( _aTextures, aStages ) );
		CHECK_RETURN( _cDataManager.Link( pProg ) );
		return true;
	}


	
//----------------------- CConstProgramData -----------------------//
	inline CConstProgramData::CConstProgramData():
		_uUBIndex(-1), _uBindingIndex(-1), _uDataSize(0), _eDataType(e_program_data::UNKNOWN), _bLock(false)
	{
	}

	inline CConstProgramData::~CConstProgramData()
	{
	}
	
	inline void CConstProgramData::Apply()
	{
		RENDER()->UBOManager()->Bind( _uUBIndex, _uBindingIndex );
	}

	inline bool CConstProgramData::Create()
	{
		return true;
	}

	inline void CConstProgramData::Destroy()
	{
		_uUBIndex		= -1;
		_uBindingIndex	= -1;
		_uDataSize		= 0;
		_eDataType		= e_program_data::UNKNOWN;
		_bLock			= false;
	}

	inline bool CConstProgramData::SetData(e_program_data::type eType, CUniformManager::idx_off_uni_c_array_t &aIdxOff, usize uDataSize)
	{
		CHECK_VALUE( !_bLock );
		CHECK_RETURN( RENDER()->UBOManager()->CreateConstUB( aIdxOff, uDataSize, _uUBIndex ) );
		_eDataType	= eType;
		_uDataSize	= uDataSize;
		return true;
	}

	inline bool CConstProgramData::SetData(e_program_data::type eType, bool bConst, const uni_array<const uint8> &aData)
	{
		CHECK_VALUE( !_bLock );
		CHECK_RETURN( RENDER()->UBOManager()->CreateConstUB( aData.ptr(), aData.Size(), _uUBIndex ) );
		_eDataType	= eType;
		_uDataSize	= aData.Size();
		return true;
	}

	inline bool CConstProgramData::Link(const IProgramPtr &pProgram)
	{
		IProgram::TUniformBlock	const *		p_ublock = nullptr;
		CHECK_RETURN( pProgram->GetUniformBlock( _eDataType, p_ublock ) );

		_uBindingIndex = p_ublock->uBindingIndex;
		CHECK_VALUE( _uDataSize == p_ublock->uSize );
		return true;

	}
	
	inline void CConstProgramData::Lock(bool bLock)
	{
		_bLock = bLock;
	}
	/*
	template <typename T>
	inline T &  CConstProgramData::GetData()
	{
		ASSERT( !_bLock );
		STATIC_ASSERT( sizeof(T) == _aData.Size() );
		return *(T *)_aData.ptr();
	}

	template <typename T>
	inline T const &  CConstProgramData::GetData() const
	{
		STATIC_ASSERT( sizeof(T) == _aData.Size() );
		return *(const T *)_aData.ptr();
	}*/

//-------------------------------------------------------------------

}	// Resources
}	// Engine

#endif	// _UX_MATERIAL_LAYER_H